﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Runtime.InteropServices;
using System.IO;
using JsonFx;
using System.Text.RegularExpressions;
using System.Web;
using Google.GData.Photos;

namespace Picasa.Faces
{
    public class Program
    {
        #region WinInet

        [DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]
        static extern bool InternetGetCookie(string lpszUrlName, string lpszCookieName,
             StringBuilder lpszCookieData, [MarshalAs(UnmanagedType.U4)] out int lpdwSize);

        private static void AddCookies(CookieContainer cookies, Uri uri)
        {
            int datasize = 4096;

            StringBuilder cookieData = new StringBuilder(datasize);

            if (!InternetGetCookie(uri.ToString(), null, cookieData, out datasize))
            {
                if (datasize < 0)
                {
                    return;
                }

                cookieData = new StringBuilder(datasize);

                if (!InternetGetCookie(uri.ToString(), null, cookieData, out datasize))
                    return;
            }

            if (cookieData.Length > 0)
            {
                cookies.SetCookies(uri, cookieData.ToString().Replace(';', ','));
            }
        }

        private static CookieContainer GetCookies(params Uri[] uris)
        {
            CookieContainer cookies = new CookieContainer();
            foreach (Uri uri in uris)
            {
                AddCookies(cookies, uri);
            }
            return cookies;
        }

        #endregion

        public class PicasaItem
        {
            public string id;
            public string dispname;
            public string name;
            public string email;
            public string iconicshapeid;
            public string iconicshapeurl;
        }

        public class ClusterResponse
        {
            public int totalclusters;
            public string clusterblocktoken;
            public int nomoreforwardsclusters;
            public PicasaCluster[] clusters;
            public PicasaFace[] faces;
            public PicasaSubjectSuggest[] subjects;
        }

        public class PicasaSubjectSuggest
        {
            public string id;
            public string faceid;
            public string name;
            public string photoid;
            public double confidencescore;
        }

        public class PicasaSubject
        {
            public string subjectid;
            public string subjectsearchurl;
        }

        public class FaceResponse
        {
            public ClusterResponse[] clusters;
        }

        public class PicasaCluster
        {
            public string shapeid;
            public string photoid;
            public string clusterid;
            public string clustertype;
            public string numshapes;
            public string url;
        }

        public class PicasaFace
        {
            public string id;
            public string url;
            public string photoid;
            public string photourl;
            public string clusteroffset;

            // HACK: just to make it easy to store subjects with Face.
            public PicasaSubjectSuggest[] subjects;
        }

        const string PROFILE_PREFIX = "var _profile_load = 0;";
        const int SKIP = 30;
        const string PROFILE_SUFFIX = ";\nvar albumStats =";

        const string CLUSTER_PREFIX = "pwa.clusterViewLoad(";
        const string CLUSTER_PREFIX2 = "while(1);";
        const string CLUSTERS_SUFFIX = ");\n</script>";

        static string GetClustersJson(string data)
        {
            string result = data.Substring(data.IndexOf(CLUSTER_PREFIX) + CLUSTER_PREFIX.Length);
            //result = result.Substring(result.IndexOf(CLUSTER_PREFIX2) + CLUSTER_PREFIX2.Length);
            return "[" + result.Substring(0, result.IndexOf(CLUSTERS_SUFFIX)) + "]";
        }

        static string GetFilePath(string fileName)
        {
            return Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), fileName);
        }

        static void Save(string fileName, string data)
        {
            File.WriteAllText(GetFilePath(fileName), data);
        }

        static void Append(string fileName, string data)
        {
            File.AppendAllText(GetFilePath(fileName), data);
        }

        static void AppendLine(string fileName, string line)
        {
            Append(fileName, line + "\r\n");
        }

        static void SaveLines(string fileName, ICollection<string> lines)
        {
            Save(fileName, string.Join("\r\n", lines.ToArray()));
        }

        static string[] ReadLines(string fileName)
        {
            return File.ReadAllLines(GetFilePath(fileName));
        }
        static void OutputClastersJson(string data)
        {
            Save("clusters.txt", GetClustersJson(data));
        }

        static string GetProfileJson(string data)
        {
            string result = data.Substring(data.IndexOf(PROFILE_PREFIX) + PROFILE_PREFIX.Length);
            result = result.Substring(SKIP);
            result = result.Substring(0, result.IndexOf(PROFILE_SUFFIX));
            return result;
        }

        static void OutputProfileJson(string data)
        {
            Save("profile.txt", GetProfileJson(data));
        }

        static string GetResponse(HttpWebRequest request)
        {
            Stream stream = request.GetResponse().GetResponseStream();
            MemoryStream ms = new MemoryStream();
            byte[] buffer = new byte[4096];
            int count;
            while ((count = stream.Read(buffer, 0, buffer.Length)) > 0)
            {
                ms.Write(buffer, 0, count);
            }
            string data = Encoding.UTF8.GetString(ms.ToArray());
            return data;
        }

        static HttpWebRequest CreateRequest(string url)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.CookieContainer = GetCookies(new Uri(url), new Uri("http://www.google.com"));
            return request;
        }

        static HttpWebRequest CreateRequest(string url, string cookie)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);
            request.CookieContainer = new CookieContainer();
            request.CookieContainer.SetCookies(new Uri(url), cookie);
            return request;
        }

        static string DoPost(HttpWebRequest request, string formData)
        {
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";
            byte[] postData = Encoding.UTF8.GetBytes(formData);
            request.ContentLength = postData.Length;
            Stream stream = request.GetRequestStream();
            stream.Write(postData, 0, postData.Length);
            stream.Close();
            string data = GetResponse(request);
            return data;
        }

        static string Post(string url, string formData)
        {
            HttpWebRequest request = CreateRequest(url);
            return DoPost(request, formData);
        }

        static string Post(string url, string formData, string cookie)
        {
            HttpWebRequest request = CreateRequest(url, cookie);
            return DoPost(request, formData);
        }

        static string Get(string url)
        {
            HttpWebRequest request = CreateRequest(url);
            return GetResponse(request);
        }

        const string FACE_PREFIX = "while(1);";

        static string PostPaging(int index)
        {
            string data = Post("http://picasaweb.google.com/lh/nameTagPaging",
                string.Format("cmax=1&fmax=0&clusterid={0}&numsgs=3", index));
            //cmax=1&fmax=0&clusterid=1000010&numsgs=3&aid=5264063463586695409

            Save("paging.txt", data);
            string json = data.Substring(data.IndexOf(FACE_PREFIX) + FACE_PREFIX.Length);
            return json;
        }

        static void PostClustering()
        {
            string data = Post("http://picasaweb.google.com/lh/nameTagClustering",
                //"csmax=14&ts=1225635023008&cbtoken=v1%2F1000263%2F1000319%2Fdrh_a3143NsgUQuVlaG9c--8i2w&cbdir=forward"
                //"csmax=14&ts=1225635023008&cbtoken=v1%2F1000510%2F1000570%2FpXI3WJiv99wU2TzNsfo-tkTLgog&cbdir=forward"
                //"csmax=14&ts=1225635023008&cbtoken=v1%2F1000571%2F1000631%2FnVzdKKn31W782OpqNVClN6mMqJI&cbdir=forward"
                "csmax=14&ts=1225638580241&cbtoken=v1%2F0%2F49%2F5rejYyCRC-czOiEcSsRVq6fx32E&cbdir=forward"
                );
            Save("clustering.txt", data);
        }

        static List<string> GetPatterns(string data, Regex regex)
        {
            List<string> result = new List<string>();
            foreach (string line in data.Split('\n'))
                foreach (Match m in regex.Matches(line))
                {
                    result.Add(m.Value);
                }
            return result;
        }

        const string PHOTO_REGEX_PATTERN = @"http://[a-zA-Z0-9]+\.ggpht.com/[^/]+/[^/]+/[^/]+/[^/]+/[^/]+.jpg";
        static readonly Regex PHOTO_REGEX = new Regex(PHOTO_REGEX_PATTERN, RegexOptions.Compiled);

        static List<string> GetAlbumPhotoUrls(string albumUrl)
        {
            string data = Get(albumUrl);
            return GetPatterns(data, PHOTO_REGEX);
        }

        static void DownloadSmallPhotos(string albumUrl)
        {
            var urls = GetAlbumPhotoUrls(albumUrl);
            urls.ForEach(url => Console.WriteLine(url));

        }

        const string ALBUM_REGEX_PATTERN = @"http:\\x2F\\x2Fpicasaweb.google.com[a-zA-Z0-9?_\\=\-]+";
        static readonly Regex ALBUM_REGEX = new Regex(ALBUM_REGEX_PATTERN, RegexOptions.Compiled);

        static List<string> GetAlbumUrls()
        {
            string data = Get("http://picasaweb.google.com/home");
            return GetPatterns(data, ALBUM_REGEX);
        }

        static List<string> GetPhotoUrls()
        {
            List<string> albomUrls = GetAlbumUrls();
            List<string> urls = new List<string>();
            foreach (string albumUrl in albomUrls)
            {
                if (!albumUrl.Contains("authkey="))
                {
                    continue;
                }
                string decodedUrl = HttpUtility.UrlDecode(albumUrl.Replace("\\x", "%"));
                List<string> current = GetAlbumPhotoUrls(decodedUrl);
                Console.WriteLine(current.Count);
                urls.AddRange(current);
            }
            return new HashSet<string>(urls).ToList();
        }

        static Dictionary<string, string> GetPicasaIdToName()
        {
            Dictionary<string, string> idToName = new Dictionary<string, string>();
            string userName = "nedajil@gmail.com";
            Console.Write("Enter your password:");
            string password = Console.ReadLine();
            PicasaService service = new PicasaService("exampleCo-exampleApp-1");
            service.setUserCredentials("nedajil@gmail.com", password);

            AlbumQuery query = new AlbumQuery(PicasaQuery.CreatePicasaUri(userName));

            PicasaFeed feed = service.Query(query);

            foreach (PicasaEntry entry in feed.Entries)
            {
                Console.WriteLine(entry.Title.Text);
                AlbumAccessor ac = new AlbumAccessor(entry);
                Console.WriteLine(ac.NumPhotos);

                PhotoQuery photoQuery = new PhotoQuery(PicasaQuery.CreatePicasaUri(userName, entry.Title.Text));

                PicasaFeed photoFeed = service.Query(photoQuery);

                foreach (PicasaEntry photo in photoFeed.Entries)
                {
                    idToName[photo.Id.Uri.Content] = photo.Title.Text;
                }
            }

            return idToName;
        }

        static void SaveOriginalPhotoUrls()
        {
            List<string> photoUrls = new List<string>();
            var photoIdToName = GetPicasaIdToName();

            foreach (var photoId in photoIdToName.Keys)
            {
                photoUrls.Add(photoId + "/" + photoIdToName[photoId]);
            }
            Save("originalPhotoUrls.txt", String.Join("\r\n", photoUrls.ToArray()));
        }

        static void DownloadSmallPhotos()
        {
            //List<string> photoUrls = GetPhotoUrls();
            //Save("originalPhotoUrls.txt", String.Join("\r\n", photoUrls.ToArray()));
            string data = Get("http://picasaweb.google.com/lh/clusterView");
            string json = GetClustersJson(data);
            Save("clusters.txt", json);
            object[] array = Json.ToObject<object[]>(json);

            ClusterResponse clusterResponse = Json.Convert<ClusterResponse>(array[0]);



            FaceResponse response = Json.Convert<FaceResponse>(array[1]);
            Save("parsed_clusters.txt", Json.ToJson(response));
            List<string> faceUrls = new List<string>();
            foreach (PicasaFace face in response.clusters[0].faces)
            {
                faceUrls.Add(face.url);
            }

        }

        static FaceResponse GetCluster(int index)
        {
            try
            {
                string json = PostPaging(index);
                return Json.ToObject<FaceResponse>(json);
            }
            catch (Exception ex)
            {
                return null;
            }
        }

        static Dictionary<string, PicasaFace> GetFaceIdToUrls(int count, string fileName)
        {
            Dictionary<string, PicasaFace> idToUrls = new Dictionary<string, PicasaFace>();
            int error = 0;
            for (int shift = 0; shift < 6; shift++)
            {
                for (int i = 0; i < count; i++)
                {
                    FaceResponse faceResponse = GetCluster(shift * 1000000 + i);
                    if (faceResponse == null)
                    {
                        error++;
                        if (error > 10)
                        {
                            error = 0;
                            break;
                        }
                        continue;
                    }

                    if (faceResponse.clusters.Length == 0)
                    {
                        break;
                    }
                    foreach (var cluster in faceResponse.clusters)
                    {
                        if (cluster.faces != null)
                        {
                            foreach (var face in cluster.faces)
                            {
                                face.subjects = cluster.subjects;
                                idToUrls[face.id] = face;
                                AppendLine(fileName, face.url + "\t" + face.photoid);
                            }
                        }
                    }
                }
            }

            return idToUrls;
        }

        static void DownloadFaceUrls(int count)
        {
            var faceUrls = GetFaceIdToUrls(count, "faceUrls.txt");
            //Save("faceUrls.txt", String.Join("\r\n", faceUrls.ToArray()));
        }

        const string PHOTO_CHUNKS_PATTERN = @"photoid/(\d+)/([^.]+).jpg";
        static readonly Regex PHOTO_CHUNKS_REGEX = new Regex(PHOTO_CHUNKS_PATTERN, RegexOptions.Compiled);

        const string FACE_CHUNKS_PATTERN = @"([^\t]+)\t([a-zA-Z0-9]+)";
        static readonly Regex FACE_CHUNKS_REGEX = new Regex(FACE_CHUNKS_PATTERN, RegexOptions.Compiled);

        static Dictionary<string, string> LoadOriginalIdToName()
        {
            string[] original = ReadLines("originalPhotoUrls.txt");
            Dictionary<string, string> idToName = new Dictionary<string, string>();
            foreach (var url in original)
            {
                MatchCollection matches = PHOTO_CHUNKS_REGEX.Matches(url);
                if (PHOTO_CHUNKS_REGEX.IsMatch(url))
                {
                    string id = matches[0].Groups[1].Value;
                    string name = matches[0].Groups[2].Value;
                    idToName[id] = name;
                }
            }
            return idToName;
        }

        static void SaveNonFacePhotos()
        {
            string[] original = ReadLines("originalPhotoUrls.txt");
            string[] faces = ReadLines("faceUrls.txt");
            Dictionary<string, string> idToName = new Dictionary<string, string>();
            Dictionary<string, string> idToUrl = new Dictionary<string, string>();
            HashSet<string> allIds = new HashSet<string>();
            HashSet<string> facedIds = new HashSet<string>();
            Dictionary<string, string> faceUrlToName = new Dictionary<string, string>();
            foreach (var url in original)
            {
                MatchCollection matches = PHOTO_CHUNKS_REGEX.Matches(url);
                if (PHOTO_CHUNKS_REGEX.IsMatch(url))
                {
                    string id = matches[0].Groups[1].Value;
                    string name = matches[0].Groups[2].Value;
                    idToName[id] = name;
                    idToUrl[id] = url;
                    allIds.Add(id);
                }
            }

            foreach (var line in faces)
            {
                MatchCollection matches = FACE_CHUNKS_REGEX.Matches(line);
                if (FACE_CHUNKS_REGEX.IsMatch(line))
                {
                    string url = matches[0].Groups[1].Value;
                    string id = matches[0].Groups[2].Value;
                    id = ulong.Parse(id, System.Globalization.NumberStyles.HexNumber).ToString();
                    if (idToName.ContainsKey(id))
                    {
                        faceUrlToName[url] = idToName[id];
                        facedIds.Add(id);
                    }
                }
            }

            Save("face-names.txt", Json.ToJson(faceUrlToName));
            HashSet<string> nonFaceIds = new HashSet<string>(allIds);
            nonFaceIds.ExceptWith(facedIds);

            List<string> nonFaceUrls = new List<string>();
            nonFaceIds.ToList().ForEach(id => nonFaceUrls.Add(idToUrl[id]));
            SaveLines("non-face-urls.txt", nonFaceUrls);
        }

        static PicasaSubject CreateSubject(string name)
        {
            string data = Post("http://picasaweb.google.com/lh/createSubject?tok=K8DVyPexY8oUJzHO8lAko2_jFvU",
                string.Format("sname={0}&dname={0}&ndnick=false", HttpUtility.UrlEncode(name)),
                "lh=DQAAAGwAAAAHZvXFLEpqw-kl_O3T6MDLGQvIz_0Sjqi29jJSp3JjWnqn9J5MpQHzCWbMLHAqdq9FAopdiBEvvEsWYV7UwX0u2Vz9Kgejag4pWj2yDAu6qwaHSKgYHgwYCjr7x3XY0eUXiHr-SPhbr8ao87xzi5id; _rtok=G0vbj62KHXYq; PREF=ID=cb244da5862758c3:TM=1216156867:LM=1224578067:GM=1:S=O37tWAvXoBe1BDtL; NID=15=KXmvfZ2QJuT9DJhvb6HEF7gjGLHxQCcnIlomCjF3SOrVaUQFNRzwLmSsTeZ82DjJNFySNy0vot7BwFj1DNAU3OLhbHtzQHp83jFEMV2cmqzE1XVo-x76GHtF2L4rn5Qa; SID=DQAAAGsAAABuzv00PN-hotZpM9hCw10OamfXoyI_5J1TGcLFuCnqVZTknO2nyYRu8vd5imIZOxqJBty_K-3FeyPLhd0SEhb6Bu2XIXDw0RKkobsPzxDAXM8ibKEa8q-SwtADzg-wT8vKo8hqPHn7iT-aVtGwMIOP; S=photos_html=mvKDC-FVBx4DilFb8fWijQ");
            PicasaSubject subject = Json.ToObject<PicasaSubject>(data);
            return subject;
        }

        static void UpdateSubject(string subjectid, params string[] faceIds)
        {
            Post("http://picasaweb.google.com/lh/updateSubject?tok=IUEIq-8eXNfaT9dcuQafLV5ARtQ",
                string.Format("opt=MOVE_FACES&subjectid={0}&fids={1}&ctype=TEXT_BOX&ptype=CLUSTER_VIEW", 
                subjectid, string.Join(",", faceIds)),
                "lh=DQAAAGwAAADCvG1nmeYxwQKZ0ZFr3lLQb0ssF663_7ZEbnayi0NdgrXkI-Oo0cwGiixiiay8cQA6X230EuZ8BuKlrIhSuSCScljxIWEbbTptOaklVDemB22QNiLuhjuCwLn6QZpzQc_dJ2pfudocVqLYj9kKmihe; SID=DQAAAGsAAADUjopgtgj30eNrWwATX8orva3g0caA6qGeZIlvzxEaLcRryV78Tns296LBIQ_kee8-CQMFncVGQPLIGZbit99Y-kMAFxeF3Ln_PiKJ2WljpljB8R8cVMX_uyf7BSXWJO5m3Pajrml9kPIu_iEcvmmQ; PREF=ID=4262736a37a9a0e1:TM=1225649954:LM=1225710752:GM=1:S=oPIjfNZ9f2wgE8Sv; _rtok=JhPeuoX9L-n0; S=photos_html=MRYzvXvSD7A8BnqlJGUhcQ:quotestreamer=J16BLAeKoQAGf02kNy01lQ");
        }

        static void MarkPhotos()
        {
            var idToFace = GetFaceIdToUrls(10, "lala.txt");
            var idToName = LoadOriginalIdToName();
            var was = false;
            foreach (PicasaFace face in idToFace.Values)
            {
                string id = ulong.Parse(face.photoid, System.Globalization.NumberStyles.HexNumber).ToString();
                string name = "?";
                if (idToName.ContainsKey(id))
                {
                    name = idToName[id];
                }
                string suggest = "<no ideas>";
                if (face.subjects != null && face.subjects.Length > 0)
                {
                    foreach (var subject in face.subjects)
                    {
                        suggest += string.Format("({0}, {1}) ", subject.name, subject.confidencescore);
                    }
                }
                Console.WriteLine("{0} - {1} - {2}", face.url, name, suggest);
                //if (name.Contains("raketa") && !was)
                //{
                //    was = true;
                //    //UpdateSubject(CreateSubject(name.Trim("0123456789_".ToCharArray())).subjectid, face.id);
                //    UpdateSubject("3831540709374022025", face.id);
                //}
            }
        }

        static void Main(string[] args)
        {
            MarkPhotos();
            //SaveNonFacePhotos();
            //SaveOriginalPhotoUrls();
            //DownloadFaceUrls(5000);
            //DownloadSmallPhotos();
            //PostClustering();
            //PostPaging(0);
            //GetCluster(0);
            //HttpWebRequest request = CreateRequest("http://picasaweb.google.com/lh/clusterView");
            //string data = GetResponse(request);
            //OutputClastersJson(data);
            //string json = GetProfileJson(data);
            //PicasaItem [] items = Json.ToObject<PicasaItem[]>(json);
            //foreach (PicasaItem item in items)
            //{
            //    Console.WriteLine("{0} {1} {2}", item.dispname, item.email, item.iconicshapeurl);
            //}
        }
    }
}
